﻿using Hylasoft.Opc.Common;
using Hylasoft.Opc.Da;
using Hylasoft.Opc.Ua;
using Opc.Da;
using SD.AOP.Core.Mediators;
using SD.AOP.Core.Models.Entities;
using SD.Common;
using SD.Infrastructure.CrontabBase;
using SD.OpcProxy.Domain.Entities;
using SD.OpcProxy.OpcService.Extensions;
using SD.OpcProxy.OpcService.Proxy;
using SD.OpcProxy.Repository.Base;
using SD.OpcProxy.ValueObjects;
using SD.OpcProxy.ValueObjects.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Transactions;

namespace SD.OpcProxy.OpcService.Crontabs
{
    /// <summary>
    /// Ping定时任务执行者
    /// </summary>
    public class PingCrontabExecutor : CrontabExecutor<PingCrontab>
    {
        #region # 字段及构造器

        /// <summary>
        /// 同步锁
        /// </summary>
        private static readonly object _Sync = new object();

        #endregion


        //Public

        #region # 执行任务 —— override void Execute(PingCrontab crontab)
        /// <summary>
        /// 执行任务
        /// </summary>
        /// <param name="crontab">定时任务</param>
        public override void Execute(PingCrontab crontab)
        {
            //日志处理
            base._logAppender.Append("［OPC服务器］" + crontab.Url + Environment.NewLine +
                                     "［连接时间］" + DateTime.Now + Environment.NewLine);

            ConnectionStatus status = this.Connect(crontab.Url);

            if (status == ConnectionStatus.Success)
            {
                try
                {
                    this.InitProxyMonitor(crontab.Url);

                    //日志处理
                    base._logAppender.Append("［连接状态］" + "成功" + Environment.NewLine);
                }
                catch (Exception ex)
                {
                    this.CleanProxyMonitor(crontab.Url);
                    this.DisposeOpcClient(crontab.Url);

                    #region # 日志处理

                    ExceptionLog exceptionLog = new ExceptionLog
                    {
                        ExceptionMessage = $"初始化OPC服务代理异常：{ex.Message}",
                        ExceptionInfo = ex.ToString(),
                        InnerException = ex.InnerException?.ToString(),
                        ExceptionType = ex.GetType().FullName,
                        ArgsJson = "[]",
                        Namespace = this.GetType().Namespace,
                        ClassName = this.GetType().Name,
                        MethodName = MethodBase.GetCurrentMethod().Name,
                        MethodType = "实例",
                        OccurredTime = DateTime.Now,
                        IPAddress = CommonExtension.GetLocalIPAddress()
                    };

                    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress))
                    {
                        LogMediator.Write(exceptionLog);
                        scope.Complete();
                    }

                    #endregion

                    //日志处理
                    base._logAppender.Append("［连接状态］" + "初始化异常" + Environment.NewLine);
                }
                return;
            }
            if (status == ConnectionStatus.Failed)
            {
                this.CleanProxyMonitor(crontab.Url);

                //日志处理
                base._logAppender.Append("［连接状态］" + "失败" + Environment.NewLine);
            }
            if (status == ConnectionStatus.Connected)
            {
                this.CheckOpcServer(crontab.Url);

                //日志处理
                base._logAppender.Append("［连接状态］" + "已连接" + Environment.NewLine);
            }
        }
        #endregion


        //Private

        #region # 连接服务器 —— ConnectionStatus Connect(string url)
        /// <summary>
        /// 连接服务器
        /// </summary>
        private ConnectionStatus Connect(string url)
        {
            lock (PingCrontabExecutor._Sync)
            {
                //判断可用的客户端是否已存在
                if (OpcProxyExtension.ClientVisitor.ContainsKey(url) && OpcProxyExtension.ClientVisitor[url].OpcClient.Status == OpcStatus.Connected)
                {
                    //检测客户端
                    IOpcClient opcClient = OpcProxyExtension.ClientVisitor[url].OpcClient;
                    bool qualified = this.CheckOpcClient(url, opcClient);

                    if (!qualified)
                    {
                        //释放客户端
                        this.DisposeOpcClient(url);

                        return ConnectionStatus.Failed;
                    }

                    //已连接
                    return ConnectionStatus.Connected;
                }

                using (DbSession dbSession = new DbSession())
                {
                    OpcServer opcServer = dbSession.Set<OpcServer>().Single(x => x.Number == url);
                    IOpcClient opcClient = opcServer.GetClient();

                    try
                    {
                        TimeSpan timeout = new TimeSpan(0, 0, 0, opcServer.Timeout);
                        bool succeed = Task.Run(() =>
                        {
                            opcClient.Connect();

                            //添加OPC客户端字典
                            OpcServer pocoOpcServer = new OpcServer(opcServer.Name, opcServer.Mode, opcServer.Number, opcServer.PingInterval, opcServer.MonitorInterval, opcServer.Timeout);
                            if (OpcProxyExtension.ClientVisitor.ContainsKey(url))
                            {
                                OpcProxyExtension.ClientVisitor[url].OpcClient.Dispose();
                                OpcProxyExtension.ClientVisitor[url] = (pocoOpcServer, opcClient);
                            }
                            else
                            {
                                OpcProxyExtension.ClientVisitor.Add(url, (pocoOpcServer, opcClient));
                            }
                        }).Wait(timeout);

                        if (succeed)
                        {
                            //连接成功
                            return ConnectionStatus.Success;
                        }

                        throw new TimeoutException($"连接服务器\"{url}\"超时！");
                    }
                    catch (Exception ex)
                    {
                        Task.Run(() => opcClient.Dispose());

                        #region # 日志处理

                        ExceptionLog exceptionLog = new ExceptionLog
                        {
                            ExceptionMessage = $"连接OPC服务器异常：{ex.Message}",
                            ExceptionInfo = ex.ToString(),
                            InnerException = ex.InnerException?.ToString(),
                            ExceptionType = ex.GetType().FullName,
                            ArgsJson = "[]",
                            Namespace = this.GetType().Namespace,
                            ClassName = this.GetType().Name,
                            MethodName = MethodBase.GetCurrentMethod().Name,
                            MethodType = "实例",
                            OccurredTime = DateTime.Now,
                            IPAddress = CommonExtension.GetLocalIPAddress()
                        };

                        using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress))
                        {
                            LogMediator.Write(exceptionLog);
                            scope.Complete();
                        }

                        #endregion

                        //连接失败
                        return ConnectionStatus.Failed;
                    }
                }
            }
        }
        #endregion

        #region # 初始化代理监听 —— void InitProxyMonitor(string url)
        /// <summary>
        /// 初始化代理监听
        /// </summary>
        private void InitProxyMonitor(string url)
        {
            //监听代理
            IOpcClient opcClient = OpcProxyExtension.ClientVisitor[url].OpcClient;
            ProxyVariableGroup proxyVariableGroup = OpcProxyExtension.ProxyVariableGroups[url];
            IDictionary<string, ProxyVariable> proxyVariables = proxyVariableGroup.ProxyVariables.ToDictionary(x => x.Number, x => x);

            opcClient.MonitorChanges(proxyVariables.Keys, (readEvents, unsubscribe) =>
            {
                foreach (KeyValuePair<string, ReadEvent> kv in readEvents)
                {
                    ProxyVariable proxyVariable = proxyVariables[kv.Key];
                    ReadEvent readEvent = kv.Value;

                    this.OnDataChanged(proxyVariable, readEvent);
                }
            });
        }
        #endregion

        #region # 清理代理监听 —— void CleanProxyMonitor(string url)
        /// <summary>
        /// 清理代理监听
        /// </summary>
        private void CleanProxyMonitor(string url)
        {
            //清空变量值
            ProxyVariableGroup proxyVariableGroup = OpcProxyExtension.ProxyVariableGroups[url];
            foreach (ProxyVariable proxyVariable in proxyVariableGroup.ProxyVariables)
            {
                proxyVariable.SetValue(Constants.Nil);
            }
        }
        #endregion

        #region # 数据变更事件 —— void OnDataChanged(ProxyVariable proxyVariable...
        /// <summary>
        /// 数据变更事件
        /// </summary>
        private void OnDataChanged(ProxyVariable proxyVariable, ReadEvent readEvent)
        {
            string dataType = readEvent.Value?.GetType().FullName;
            string previousValue = proxyVariable.Value?.ToString();
            string currentValue = readEvent.Value?.ToString();

            proxyVariable.SetValue(readEvent.Value);

            if (string.IsNullOrWhiteSpace(currentValue))
            {
                return;
            }
            if (currentValue == Constants.Nil)
            {
                return;
            }
            if (proxyVariable.KeepRecord && previousValue != currentValue)
            {
                //记录
                Task.Run(() => this.LogDataChange(proxyVariable, dataType, previousValue, currentValue));
            }
        }
        #endregion

        #region # 记录数据变更 —— void LogDataChange(ProxyVariable proxyVariable...
        /// <summary>
        /// 记录数据变更
        /// </summary>
        private void LogDataChange(ProxyVariable proxyVariable, string dataType, string previousValue, string currentValue)
        {
            using (DbSession dbSession = new DbSession())
            {
                string previousValueDescription;
                string currentValueDescription;

                #region # 值描述取值

                const string initialNull = "初始空值";
                const string connectInterruptd = "连接中断";

                if (previousValue == null)
                {
                    previousValueDescription = initialNull;
                }
                else if (previousValue == Constants.Nil)
                {
                    previousValueDescription = connectInterruptd;
                }
                else if (proxyVariable.ValueDescriptions.ContainsKey(previousValue))
                {
                    previousValueDescription = proxyVariable.ValueDescriptions[previousValue];
                }
                else
                {
                    previousValueDescription = null;
                }
                if (currentValue == Constants.Nil)
                {
                    currentValueDescription = connectInterruptd;
                }
                else if (currentValue == null)
                {
                    currentValueDescription = initialNull;
                }
                else if (proxyVariable.ValueDescriptions.ContainsKey(currentValue))
                {
                    currentValueDescription = proxyVariable.ValueDescriptions[currentValue];
                }
                else
                {
                    currentValueDescription = null;
                }

                #endregion

                VariableHistory record = new VariableHistory(proxyVariable.Number, proxyVariable.Name, dataType, previousValue, previousValueDescription, currentValue, currentValueDescription, proxyVariable.Unit);

                dbSession.Set<VariableHistory>().Add(record);
                dbSession.SaveChanges();
            }
        }
        #endregion

        #region # 检测OPC服务端 —— void CheckOpcServer(string url)
        /// <summary>
        /// 检测OPC服务端
        /// </summary>
        private void CheckOpcServer(string url)
        {
            using (DbSession dbSession = new DbSession())
            {
                OpcServer opcServer = dbSession.Set<OpcServer>().Single(x => x.Number == url);
                IOpcClient opcClient = opcServer.GetClient();

                try
                {
                    TimeSpan timeout = new TimeSpan(0, 0, 0, opcServer.Timeout);
                    bool succeed = Task.Run(() =>
                    {
                        //测试连接
                        opcClient.Connect();
                        opcClient.Dispose();

                    }).Wait(timeout);

                    if (!succeed)
                    {
                        throw new TimeoutException($"连接服务器\"{url}\"超时！");
                    }

                    //日志处理
                    base._logAppender.Append("［服务端检测时间］" + DateTime.Now + Environment.NewLine +
                                             "［服务端连接状态］" + "正常" + Environment.NewLine);
                }
                catch (Exception ex)
                {
                    #region # 日志处理

                    ExceptionLog exceptionLog = new ExceptionLog
                    {
                        ExceptionMessage = $"检测OPC服务器异常：{ex.Message}",
                        ExceptionInfo = ex.ToString(),
                        InnerException = ex.InnerException?.ToString(),
                        ExceptionType = ex.GetType().FullName,
                        ArgsJson = "[]",
                        Namespace = this.GetType().Namespace,
                        ClassName = this.GetType().Name,
                        MethodName = MethodBase.GetCurrentMethod().Name,
                        MethodType = "实例",
                        OccurredTime = DateTime.Now,
                        IPAddress = CommonExtension.GetLocalIPAddress()
                    };

                    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress))
                    {
                        LogMediator.Write(exceptionLog);
                        scope.Complete();
                    }

                    #endregion

                    //日志处理
                    base._logAppender.Append("［服务端检测时间］" + DateTime.Now + Environment.NewLine +
                                             "［服务端连接状态］" + "超时" + Environment.NewLine);

                    //释放客户端
                    this.DisposeOpcClient(url);
                }
            }
        }
        #endregion

        #region # 检测OPC客户端 —— void CheckOpcClient(string url...
        /// <summary>
        /// 检测OPC客户端
        /// </summary>
        private bool CheckOpcClient(string url, IOpcClient opcClient)
        {
            bool connected = false;
            int subscriptionsCount = 0;
            int activeSubscriptionsCount = 0;
            int activeItemsCount = 0;

            if (opcClient is UaClient uaClient)
            {
                connected = uaClient.Session.Connected;
                subscriptionsCount = uaClient.Session.Subscriptions.Count();
                activeSubscriptionsCount = uaClient.Session.Subscriptions.Count(x => x.CurrentPublishingEnabled);
                activeItemsCount = uaClient.Session.Subscriptions.Where(x => x.CurrentPublishingEnabled).SelectMany(x => x.MonitoredItems).Count();
            }
            if (opcClient is DaClient daClient)
            {
                connected = daClient.Server.IsConnected;
                subscriptionsCount = daClient.Server.Subscriptions.Count;

                foreach (Subscription subscription in daClient.Server.Subscriptions)
                {
                    if (subscription.Active)
                    {
                        activeSubscriptionsCount++;
                        activeItemsCount += subscription.Items.Length;
                    }
                }
            }

            //日志处理
            base._logAppender.Append("［客户端检测时间］" + DateTime.Now + Environment.NewLine +
                                     "［客户端连接状态］" + (connected ? "正常" : "中断") + Environment.NewLine +
                                     "［全部订阅数量］" + subscriptionsCount + Environment.NewLine +
                                     "［活动订阅数量］" + activeSubscriptionsCount + Environment.NewLine +
                                     "［活动变量数量］" + activeItemsCount + Environment.NewLine);

            //检测订阅
            ProxyVariableGroup proxyVariableGroup = OpcProxyExtension.ProxyVariableGroups[url];

            if (subscriptionsCount != activeSubscriptionsCount)
            {
                return false;
            }
            if (activeItemsCount != proxyVariableGroup.ProxyVariables.Count())
            {
                return false;
            }
            if (!connected)
            {
                return false;
            }

            return true;
        }
        #endregion

        #region # 释放OPC客户端 —— void DisposeOpcClient(string url)
        /// <summary>
        /// 释放OPC客户端
        /// </summary>
        private void DisposeOpcClient(string url)
        {
            if (OpcProxyExtension.ClientVisitor.ContainsKey(url))
            {
                IOpcClient specClient = OpcProxyExtension.ClientVisitor[url].OpcClient;
                OpcProxyExtension.ClientVisitor.Remove(url);

                Task.Run(() => specClient.Dispose());
            }
        }
        #endregion
    }
}
